As part of my experiments with GTK4 and Haskell, I sought an application with a picture which would report on the co-ordinates if clicked on with a mouse. For my example image, I took the Flammarion wood engraving.
gtk-picture
With Stack, I created a new single-package project gtk-picture with Main.hs:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
{-# LANGUAGE ImplicitParams #-} {-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedStrings #-} module Main ( main ) where import Control.Monad ( void ) import Data.GI.Base ( AttrOp (..), new, set ) import Data.Text ( Text ) import qualified Data.Text as T import qualified GI.Gtk as Gtk import Paths_gtk_picture ( getDataFileName ) activate :: Gtk.Application -> IO () activate app = do grid <- new Gtk.Grid [ #columnSpacing := 5 , #rowSpacing := 5 , #marginBottom := 5 , #marginEnd := 5 , #marginStart := 5 , #marginTop := 5 ] labelCoords <- mkLabel coordsIntro let onMouseClick :: Gtk.GestureClickPressedCallback onMouseClick _nPress x y = labelCoords `set` [ #label := coordsIntro <> showCoords x y ] gestureClick <- new Gtk.GestureClick [ On #pressed onMouseClick ] picture <- flammarionPicture picture `set` [ #canShrink := False ] #addController picture gestureClick #attach grid labelCoords 0 0 1 1 #attach grid picture 0 1 1 1 window <- new Gtk.ApplicationWindow [ #application := app , #title := "Clicking on a picture" , #child := grid ] window.show mkLabel :: Text -> IO Gtk.Label mkLabel label = new Gtk.Label [ #label := label , #halign := Gtk.AlignStart ] coordsIntro :: Text coordsIntro = "Mouse last clicked at: " showCoords :: Double -> Double -> Text showCoords x y = "(" <> x' <> ", " <> y' <> ")" where showCoord = T.show . (round :: Double -> Int) x' = showCoord x y' = showCoord y flammarionPicture :: IO Gtk.Picture flammarionPicture = Just <$> flammarionPngFile >>= Gtk.pictureNewForFilename flammarionPngFile :: IO FilePath flammarionPngFile = getDataFileName "Flammarion573x480.png" main :: IO () main = do app <- new Gtk.Application [ #applicationId := "com.pilgrem.gtk-picture" , On #activate (activate ?self) ] void $ app.run Nothing |
and package.yaml (extract):
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
spec-version: 0.36.0 name: gtk-picture version: 0.1.0.0 ... data-dir: resources data-files: - Flammarion573x480.png ... dependencies: - base >= 4.7 && < 5 - gi-gtk >= 4.0 - haskell-gi-base - text >= 2.1.2 ... executables: gtk-picture: main: Main.hs source-dirs: app generated-other-modules: Paths_gtk_picture ghc-options: - -threaded - -rtsopts - -with-rtsopts=-N |
and Stack project-level configuration:
|
1 2 3 4 5 6 7 8 9 10 |
snapshot: lts-22.41 # GHC 9.6.6 extra-deps: - Cabal-3.10.3.0 # As text is an extra-dep - Cabal-syntax-3.10.3.0 # As text is an extra-dep - gi-gdk-4.0.9 - gi-gsk-4.0.8 - gi-gtk-4.0.9 - parsec-3.1.17.0 # As text-2.1.2 is an extra-dep - text-2.1.2 # To use Data.Text.show |
GTK4
Including a picture was straightforward: there is a GTK4 widget Picture, and an action to create one from a file is yielded by pictureNewForFilename. I used a file Flammarion573x480.png located in a directory resources (as indicated by the package.yaml extract). I also used a Paths_gtk_picture module, autogenerated by Cabal. In that regard, I used Hpack’s modern behaviour by specifying spec-version: 0.36.0.
Handling the clicking on the picture also turned out to be straightforward. There is a GTK4 event controller GestureClick, and an action to create one is provided by new. The event controller provides a signal pressed and a callback function can be connected to it. So, it was a matter of using #addController to yield an action that added that event controller to the picture.
The result (stack exec -- gtk-picture) was as below:

Packaging
stack build reports that it has installed the executable in directory ...\gtk-picture.stack-work\install\5a333380\bin. The structure of the 5a333380 directory (being the directory returned by command stack path --local-install-root) includes:
|
1 2 3 4 5 6 7 8 9 |
bin └─gtk-picture.exe doc └─gtk-picture-0.1.0.0 └─LICENSE share └─x86_64-windows-ghc-9.6.6 └─gtk-picture-0.1.0.0 └─Flammarion573x480.png |
Executing gtk-picture in the Stack environment puts certain MSYS2 directories on the PATH. Executing it outside of that environment results in errors if those directories are not otherwise on the PATH. For example:

The ldd command, provided by MSYS2, can be used to identify the dynamic link libraries (DLL) on which gtk-picture.exe depends:
|
1 2 3 4 5 6 7 8 9 10 11 |
stack exec -- ldd ((stack path --local-install-root) + "\bin\gtk-picture.exe") ntdll.dll => /c/WINDOWS/SYSTEM32/ntdll.dll (0x7ffd9ab50000) KERNEL32.DLL => /c/WINDOWS/System32/KERNEL32.DLL (0x7ffd99260000) KERNELBASE.dll => /c/WINDOWS/System32/KERNELBASE.dll (0x7ffd98220000) ucrtbase.dll => /c/WINDOWS/System32/ucrtbase.dll (0x7ffd98610000) SHELL32.dll => /c/WINDOWS/System32/SHELL32.dll (0x7ffd997b0000) msvcp_win.dll => /c/WINDOWS/System32/msvcp_win.dll (0x7ffd97e80000) USER32.dll => /c/WINDOWS/System32/USER32.dll (0x7ffd98e60000) libgdk_pixbuf-2.0-0.dll => /mingw64/bin/libgdk_pixbuf-2.0-0.dll (0x7ffd4c8e0000) ... libsharpyuv-0.dll => /mingw64/bin/libsharpyuv-0.dll (0x7ffd3d580000) |
Each line output by ldd begins with a tab character. The DLL located in /mingw64/bin can be isolated using a regular expression:
|
1 2 3 4 5 |
stack exec -- ldd ((stack path --local-install-root) + "\bin\gtk-picture.exe") | ForEach-Object {if ($_ -match '^\t(.*?) => /mingw.*$') {$matches[1]}} libgdk_pixbuf-2.0-0.dll ... libsharpyuv-0.dll |
Further, each of those isolated DLL can be copied to the same directory as the executable:
|
1 2 3 4 5 6 7 |
stack exec -- ldd ((stack path --local-install-root) + "\bin\gtk-picture.exe") | ForEach-Object {if ($_ -Match '^\t(.*?) => /mingw.*$') {$matches[1]}} | ForEach-Object { Copy-Item (((stack path --programs) + "\msys2-20240727\mingw64\bin\")+$_) -Destination ((stack path --local-install-root) + "\bin") } |
Now, running the executable gtk-picture.exe does not result in errors. However, running the executable from Windows also results in a terminal opening. This can be avoided by specifying the ghc-option -optl-mwindows, which specifies the option -mwindows at the linking stage.